package service

import (
	"encoding/json"
	"errors"
	"github.com/dgrijalva/jwt-go"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
	"zimuzu_cvn_web_api/config"
	"zimuzu_cvn_web_api/model"
	"zimuzu_cvn_web_api/pkg/drive/redis"
	. "zimuzu_cvn_web_api/pkg/e"
)

const MaxLoginErrCount = 5   //最大登录错误次数
const MaxLoginErrIpCount = 3 //最大IP登录错误次数
const LoginExpire = 3600 * 24
const AppLoginExpire = 3600 * 24 * 30 * 30

const LoginPlatformPc = "pc"
const LoginPlatformApp = "app"
const LoginPlatformAdmin = "admin"
const LoginPlatformAll = "all"

type TokenClaims struct {
	jwt.Claims
	NickName string `json:"nick_name" mapstructure:"nick_name"`
	Uid      int64  `json:"uid" mapstructure:"uid"`
	Sign     string `json:"sign" mapstructure:"sign"`
}

//管理后台登录密码校验
//Auth: Xu
func CheckAdminLogin(account string, md5Password string) (user model.AdminUserDb, err error) {
	user, err = model.GetAdminUserByMobile(config.C.DB.DB, account)
	if err != nil {
		return user, errors.New("账号或密码错误")
	} else {
		if isBlacklist, expireTime := CheckUserLoginBlacklist(user.Id * 100000); isBlacklist {
			var timeM int
			if expireTime > 60 {
				timeM = expireTime / 60000
			} else {
				timeM = 1
			}
			return user, errors.New("登录错误次数达到上限, 请" + strconv.Itoa(timeM) + "分钟后重试")
		}
	}
	pwdEncrypt := GetPwdEncrypt(md5Password, user.Salt)
	if pwdEncrypt != user.Pwd {
		Log("info", md5Password)
		return user, errors.New("账号或密码错误！")
	}

	return user, nil
}

//密码校验
//Auth: Xu
func CheckLogin(account string, md5Password string, area int) (user model.User, err error) {
	res, err := CheckAccountReg(account, area)
	if !res {
		return user, errors.New("账号或密码错误")
	}
	user, err = model.GetUserByMobile(config.C.DB.DB, account, area)
	if err != nil {
		return user, errors.New("账号或密码错误")
	} else {
		if isBlacklist, expireTime := CheckUserLoginBlacklist(user.Uid); isBlacklist {
			var timeM int
			if expireTime > 60 {
				timeM = expireTime / 60000
			} else {
				timeM = 1
			}
			return user, errors.New("登录错误次数达到上限, 请" + strconv.Itoa(timeM) + "分钟后重试")
		}
	}
	pwdEncrypt := GetPwdEncrypt(md5Password, user.Salt)
	if pwdEncrypt != user.Password {
		Log("info", "md5=", md5Password)
		Log("info", "new=", pwdEncrypt)
		return user, errors.New("账号或密码错误！")
	}
	//缓存用户信息
	if _, errGetCache := AddUserInfoCache(user.Uid, user); errGetCache != nil {
		return user, errGetCache
	}

	return user, nil
}

// 获取二层Md5密码
//Auth: Xu
func GetPwdEncrypt(password, salt string) string {
	return Md5Str(Md5Str(password) + salt)
}

// 获取一层Md5密码
//Auth: Xu
func GetMd5PwdEncrypt(md5Password, salt string) string {
	return Md5Str(md5Password + salt)
}

//获取登录错误次数string缓存key
//Auth: Xu
func GetLoginErrCountCacheKey(userId int64, ip string) (loginErrCountCacheKey string) {
	ipUserIdHash := Md5Str(strconv.FormatInt(userId, 10) + "|" + ip)
	loginErrCountCacheKey = UserRedisPrefix + strconv.FormatInt(userId, 10) + UserLoginErrCountCacheKey + ipUserIdHash
	return
}

//获取登录错误IP SET缓存key
//Auth: Xu
func GetLoginErrIpCountCacheKey(userId int64) (loginErrCountIpCacheKey string) {
	loginErrCountIpCacheKey = UserRedisPrefix + strconv.FormatInt(userId, 10) + UserLoginErrIpCountCacheKey
	return
}

//获取用户登录黑名单标识string  缓存key
//Auth: Xu
func GetLoginBlackCacheKey(userId int64) (loignBlacklistCacheKey string) {
	loignBlacklistCacheKey = UserRedisPrefix + strconv.FormatInt(userId, 10) + UserLoginBlacklistCacheKey
	return
}

//获取用户登录token  缓存key
//Auth: Xu
func GetLoginUidPwdSignCacheKey(userId int64) (loginTokenCacheKey string) {
	loginTokenCacheKey = UserRedisPrefix + strconv.FormatInt(userId, 10) + UserLoginTokenCacheKey
	return
}

//获取用户商城登录token  缓存key
//Auth: Xu
func GetLoginShopTokenCacheKey(userId int64) (loginTokenCacheKey string) {
	loginTokenCacheKey = UserRedisPrefix + strconv.FormatInt(userId, 10) + UserLoginShopToken
	return
}

//增加登录错误次数
//Auth: Xu
func AddLoginErrCount(ip string, userId int64) (err error) {
	loginErrCountCacheKey := GetLoginErrCountCacheKey(userId, ip)
	loginCount, err := redis.Incr(loginErrCountCacheKey)
	if err != nil {
		return
	}
	if loginCount == 1 {
		redis.Expire(loginErrCountCacheKey, 3600)
	}
	//查询登录错误IP 集合缓存数量
	loginErrCountIpCacheKey := GetLoginErrIpCountCacheKey(userId)
	ipCount, err1 := redis.SCard(loginErrCountIpCacheKey)
	if err1 != nil {
		Log("err", "获取登录错误IP集合缓存失败")
	}
	if ipCount > MaxLoginErrIpCount {
		AddUserLoginBlackList(userId)
		return nil
	}
	if loginCount >= MaxLoginErrCount {
		AddUserLoginBlackList(userId)
		//更新登录错误IP集合
		_, err = redis.SAdd(loginErrCountIpCacheKey, ip)
		return
	}
	return
}

//检查登录黑名单
//Auth: Xu
func CheckUserLoginBlacklist(userId int64) (isBlacklist bool, expireTime int) {
	loignBlacklistCacheKey := GetLoginBlackCacheKey(userId)
	isBlacklist = redis.Exists(loignBlacklistCacheKey)
	if isBlacklist {
		expireTime, _ = redis.RedisPttl(loignBlacklistCacheKey)
	}
	return
}

//用户登录添加黑名单
//Auth: Xu
func AddUserLoginBlackList(userId int64) error {
	loignBlacklistCacheKey := GetLoginBlackCacheKey(userId)
	//黑名单持续时间1小时
	_, err := redis.Set(loignBlacklistCacheKey, 1, 3600)
	if err != nil {
		Log("err", "用户登录添加黑名单失败")
	}
	return err
}

//创建jwt token
//Auth: Xu
func CreateToken(platform string, userId int64, pwd string, nickName string) string {

	token := jwt.New(jwt.SigningMethodHS256)

	claims := make(jwt.MapClaims)
	claims["uid"] = userId
	claims["nick_name"] = nickName
	var Loginexpire int64
	if platform == LoginPlatformApp {
		Loginexpire = AppLoginExpire
	} else {
		Loginexpire = LoginExpire
	}
	claims["exp"] = time.Now().Unix() + Loginexpire
	uidPwdSignHash := GetUidPwdSignHash(userId, pwd)
	claims["sign"] = uidPwdSignHash
	token.Claims = claims
	tokenString, err := token.SignedString([]byte(config.C.JwtSecret))
	if err == nil {
		setLoginTokenCache(userId, tokenString, platform, Loginexpire)
		return tokenString
	} else {
		return ""
	}

}

//缓存用户登录的token
//Auth: Xu
func setLoginTokenCache(userId int64, token string, platform string, expire int64) error {
	//expireInt := Int64ToInt(expire)
	loginUidPwdSignCacheKey := GetLoginUidPwdSignCacheKey(userId)
	//缓存用户登录的token,   pc、app只允许一个设备登录
	//m1 := make(map[string]interface{})
	//m1[platform] = token
	_, err := redis.Set(loginUidPwdSignCacheKey+":"+platform, token, int(expire))
	if err != nil {
		Log("err", "缓存登录token失败")
	}
	return err
}

type LoginToken struct {
	PC  string `mapstructure:"pc"`
	APP string `mapstructure:"app"`
}

//获取缓存用户登录的token
//Auth: Xu
func GetLoginTokenCache(userId int64, platform string) (string, error) {
	//var loginToken LoginToken
	loginUidPwdSignCacheKey := GetLoginUidPwdSignCacheKey(userId) + ":" + platform
	//缓存用户登录的token
	//sign,err := redis.HGetAll(loginUidPwdSignCacheKey)
	//if err != nil{
	//	Log("err", "获取缓存登录token失败")
	//}
	//Map2Struct(loginToken, &loginToken, sign)
	token, err := redis.Get(loginUidPwdSignCacheKey)
	return token, err
}

//清空用户登录的token
//Auth: Xu
func CleartLoginTokenCache(platform string, userId int64) (err error) {
	loginUidPwdSignCacheKey := GetLoginUidPwdSignCacheKey(userId)
	//全平台推出
	if platform == LoginPlatformAll {
		_, err = redis.Delete(loginUidPwdSignCacheKey + ":" + LoginPlatformApp)
		if err != nil {
			Log("err", "清除"+LoginPlatformApp+"缓存登录token失败")
		}
		_, err = redis.Delete(loginUidPwdSignCacheKey + ":" + LoginPlatformPc)
		if err != nil {
			Log("err", "清除"+LoginPlatformPc+"缓存登录token失败")
		}
	} else {
		_, err = redis.Delete(loginUidPwdSignCacheKey + ":" + platform)
		if err != nil {
			Log("err", "清除缓存登录token失败")
		}
	}

	return err
}

//获取用户uid+密码的md5 hash
//Auth: Xu
func GetUidPwdSignHash(userId int64, pwd string) string {
	return Md5Str(strconv.FormatInt(userId, 10) + pwd)
}

type shopTokenData struct {
	Status int    `json:"status"`
	Info   string `json:"info"`
	Data   struct {
		Uid   string `json:"uid"`
		Token string `json:"token"`
	} `json:"data"`
}

//获取商城登录Token
//Auth: Xu
func GetShopToken(mobile, pwd string, area int) (shopTokenData, error) {
	shopTokenData := shopTokenData{}
	apiurl := config.C.ShopDomain + "/index.php?g=api/shop&m=index&a=mobile_login"
	areaStr := strconv.Itoa(area)

	values := url.Values{
		"accesskey": {"519f9cab85c8059d17544947k361a827"},
		"mobile":    {mobile},
		"password":  {pwd},
		"area":      {areaStr},
	}

	resp, err := http.PostForm(apiurl, values)

	if err != nil {
		return shopTokenData, errors.New("商城TOKEN请求失败")
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	json.Unmarshal(body, &shopTokenData)

	return shopTokenData, nil
}

//检测商城登录Token是否有校
//Auth: Xu
func GetShopTokenExpire(uid int64, shopToken string) (shopTokenData, error) {
	shopTokenData := shopTokenData{}
	userInfo, _ := GetUserInfo(uid)
	apiurl := config.C.ShopDomain + "/index.php?g=api/shop&m=index&a=userinfo"
	areaStr := strconv.Itoa(userInfo.PhoneArea)
	uidStr := strconv.FormatInt(uid, 10)

	values := url.Values{
		"accesskey":    {"519f9cab85c8059d17544947k361a827"},
		"uid":          {uidStr},
		"token":        {shopToken},
		"phone_number": {userInfo.PhoneNumber},
		"phone_area":   {areaStr},
	}

	resp, err := http.PostForm(apiurl, values)

	if err != nil {
		return shopTokenData, errors.New("商城TOKEN请求失败")
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	json.Unmarshal(body, &shopTokenData)
	shopTokenData.Data.Token = shopToken

	return shopTokenData, nil
}

//管理后台退出登录清除token
//Auth: Xu
func AdminLoginOutClearToken(uid int64, platform string) error {
	err := CleartLoginTokenCache(platform, uid)
	if err != nil {
		return errors.New("清楚登录缓存失败")
	}

	return nil
}

type shopLoginOutData struct {
	Status int    `json:"status"`
	Info   string `json:"info"`
}

//退出登录清除token
//Auth: Xu
func LoginOutClearToken(uid int64, platform string) error {
	err := CleartLoginTokenCache(platform, uid)
	if err != nil {
		return errors.New("清楚登录缓存失败")
	}

	//shopToken := GetShopTokenCache(uid)
	//shopLoginOutData := shopLoginOutData{}
	//uidStr := strconv.FormatInt(uid, 10)
	//resp, err := http.Get(config.C.ShopDomain + "/index.php?g=api/shop&m=index&a=logout&accesskey=519f9cab85c8059d17544947k361a827&uid=" + uidStr + "&token=" + shopToken)
	//if err != nil {
	//	return errors.New("清除商城登录token失败")
	//}
	//defer resp.Body.Close()
	//body, _ := ioutil.ReadAll(resp.Body)
	//json.Unmarshal(body, &shopLoginOutData)
	//if shopLoginOutData.Status != 1 {
	//	Log("err", "清除商城登录token失败")
	//}
	////清除主站缓存
	//resp2, err := http.Get("http://a.allappapi.com/index.php?g=api/partner&m=index&a=del_user_redis&key=dsl123sdxks309sd&uid=" + uidStr)
	//defer resp2.Body.Close()

	return nil
}

//获取注册签名缓存键
//Auth: Xu
func GetCreateRegSignCacheKey(phone string) string {
	return PublicCacheKeyPre + RedisMobileRegSignCacheKey + phone
}

//创建注册签名
//Auth: Xu
func CreateRegSign(phone string) string {
	secret := "124gad1tghasdgsdf1232"
	now := time.Now().Unix()
	sign := Md5Str(secret + phone + strconv.FormatInt(now, 10))
	cacheKey := GetCreateRegSignCacheKey(phone)
	redis.Set(cacheKey, sign, 5*60)

	return sign
}

//注册签名校验
func ValidateRegSign(phone, sign string) error {
	cacheKey := GetCreateRegSignCacheKey(phone)
	if !redis.Exists(cacheKey) {
		return errors.New("签名失效")
	}
	signCache, _ := redis.Get(cacheKey)
	if signCache == sign {
		return nil
	} else {
		return errors.New("签名错误")
	}
}

//获取注册标识缓存键
//Auth: Xu
func GetRegMarkCacheKey(uid int64) string {
	return PublicCacheKeyPre + RedisUserRegMarkCacheKey + strconv.FormatInt(uid, 10)
}

//设置注册后的标识
//Auth: Xu
func SetRegMarkCache(uid int64) {
	cacheKey := GetRegMarkCacheKey(uid)
	redis.Set(cacheKey, 1, 60)
}

//检测注册后的标识
//Auth: Xu
func CheckRegMark(uid int64) bool {
	//如果已经设置支付密码，则直接返回false,   强制校验登陆密码和验证码
	if res, _ := CheckPayPasswordExist(uid); res {
		return false
	} else {
		return true
	}
	cacheKey := GetRegMarkCacheKey(uid)
	if redis.Exists(cacheKey) {
		return true
	} else {
		return false
	}
}

//缓存商城登陆的token
//Auth: Xu
func SetShopTokenCache(uid int64, token string) {
	tokenCacheKey := GetLoginShopTokenCacheKey(uid)
	redis.Set(tokenCacheKey, token, -1)
}

//获取商城登陆的token
//Auth: Xu
func GetShopTokenCache(uid int64) (token string) {
	tokenCacheKey := GetLoginShopTokenCacheKey(uid)
	if redis.Exists(tokenCacheKey) {
		token, _ = redis.Get(tokenCacheKey)
		//先检查token是否有效
		getHttpResponse, err := GetShopTokenExpire(uid, token)
		if err != nil {
			return ""
		}
		if getHttpResponse.Status == 1021 {
			goto Loop
		} else {
			return token
		}

	} else {
		goto Loop
	}
Loop:
	userInfo, _ := GetUserInfo(uid)
	shopToken, _ := GetShopToken(userInfo.PhoneNumber, userInfo.Password, userInfo.PhoneArea)
	token = shopToken.Data.Token
	SetShopTokenCache(uid, token)
	return
}
